﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;

namespace Nuts
{
    public class Nut
    {
        protected static Texture2D   s_texNut;
        protected static SoundEffect s_sndHitGround;
        protected static SoundEffect s_sndHitHanged;
        protected static Rectangle   s_rectAcorn;
        protected static Vector2     s_spriteOriginHung;
        protected static Vector2     s_spriteOriginLoose;

        const int   SIZE = 40; // nuts proxies are square for rolling purpose :D
        const int   SHADOW_WIDTH = 34;
        const int SHADOW_THICKNESS = 10;
        const float GRAVITY = 200.0f;
        const float SPAWN_DURATION = 2.0f;
        const float GROUND_RESTITUTION = 0.3f;
        const float GROUND_ANGULAR_RESTITUTION = 0.3f;
        const float MAX_VERTICAL_VELOCITY_TO_REST = 5.0f;

        const float HIT_MIN_VOLUME = 0.3f;
        const float HIT_MIN_VELOCITY =   5.0f;
        const float HIT_MAX_VELOCITY = 100.0f;
        const float HANG_OSCILLATION_FREQ_MUL = 4.0f;
        const float HANG_OSCILLATION_SPAN = 0.5f;

        const float ANGULAR_VELOCITY_DECAY = 0.2f;

        protected enum State
        {
            SPAWNING = 0,
            HANGING,
            FREE,
            GROUND,
            COLLECTED
        };

        protected Game1     _game;
        protected Rectangle _hit_box;
        protected Vector2   _position;
        protected Vector2   _velocity;
        protected State     _state;
        protected float     _timer;

        protected float _rotation;
        protected float _angular_velocity;

        public Nut(Game1 game)
        {
            _game = game;
            _hit_box = new Rectangle(0, 0, Nut.SIZE, Nut.SIZE);
        }

        // load content for all instances of nut
        public static void LoadContent(Game1 game)
        {
            s_texNut = game.Content.Load<Texture2D>("items_00");
            s_rectAcorn = new Rectangle(209, 3, 44, 62);
            s_sndHitGround = game.Content.Load<SoundEffect>("sound_hit");
            s_sndHitHanged = game.Content.Load<SoundEffect>("sound_hit");

            s_spriteOriginHung  = new Vector2((float)s_rectAcorn.Width * 0.5f, 0.0f);
            s_spriteOriginLoose = new Vector2((float)s_rectAcorn.Width * 0.5f, (float)s_rectAcorn.Height * 0.5f);
        }

        public Rectangle GetHitBox()
        {
            return _hit_box;
        }

        public void Spawn(int X, int Y)
        {
            _state = State.SPAWNING;
            _timer = SPAWN_DURATION;

            _position.X = (float)X;
            _position.Y = (float)Y;
            UpdateHitBox();

            _rotation = 0.0f;
            _angular_velocity = 0.0f;
        }

        // ready to be cut loose :D
        public bool IsRipe()
        {
            return (_state == State.HANGING);
        }

        public bool IsOnGround()
        {
            return (_state == State.GROUND);
        }

        public void CutLose()
        {
            s_sndHitHanged.Play();

            _state = State.FREE;

            // compute cut lose direction based on hang oscillation
            _angular_velocity = Math.Sign(_rotation) * _game.RandomFloat(2.0f, 10.0f);

            _velocity = ComputeHangingNutDirection(_rotation);
            _velocity *= 50.0f;

            _position = ComputeHangingNutCenter(_rotation);
            UpdateHitBox();

            _timer = 0.0f;
        }

        public void Roll(float delta_x)
        {
            // the nut "rolled" delta_x, and delta_x is R * theta, so th
            float theta_rolled = delta_x / Nut.SIZE;
            _rotation += theta_rolled;

            _position.X += delta_x;
            UpdateHitBox();

            if(_position.X > _game.SAFE_SPOT_RIGHT.X || _position.X < _game.SAFE_SPOT_LEFT.X)
            {
                // score this nut :D
                _game.GetNuts().Remove(this);
            }            
        }

        public void Update(GameTime gametime)
        {
            float dt = (float)gametime.ElapsedGameTime.TotalSeconds;

            switch (_state)
            {
                case State.SPAWNING:
                    _timer -= dt;
                    if (_timer <= 0.0f)
                    {
                        _state = State.HANGING;
                        _timer = 0.0f;
                    }
                    break;

                case State.HANGING:
                    // oscillation timer
                    _timer += dt;
                    _rotation = (float)Math.Sin(_timer * HANG_OSCILLATION_FREQ_MUL) * HANG_OSCILLATION_SPAN;
                    break;

                case State.FREE:
                    // free fall
                    _velocity.Y += dt * GRAVITY;
                    _position   += _velocity * dt;

                    // some decay on velocity
                    _angular_velocity *= (1.0f - Math.Min(ANGULAR_VELOCITY_DECAY * dt, 1.0f));
                    _rotation += _angular_velocity * dt;

                    // TODO : check collisions with entities
                    // warning : hit box is not updated yet here

                    // ground collision
                    const float ground_collision_y = Game1.GROUND_LEVEL - Nut.SIZE/2;
                    if (_position.Y > ground_collision_y)
                    {
                        _velocity.Y *= -GROUND_RESTITUTION;
                        _position.Y = ground_collision_y;

                        _angular_velocity *= -GROUND_ANGULAR_RESTITUTION;

                        float velocity_vertical_abs = Math.Abs(_velocity.Y);

                        if (velocity_vertical_abs < MAX_VERTICAL_VELOCITY_TO_REST)
                        {
                            _velocity.Y = 0.0f;
                            _state = State.GROUND;
                        }
                        
                        // play hit sound, volume is proportional to velocity Y :D
                        float velocity_vertical_clamped = Math.Min(Math.Max(velocity_vertical_abs, HIT_MIN_VELOCITY), HIT_MAX_VELOCITY);
                        float hit_velocity_factor = (velocity_vertical_clamped - HIT_MIN_VELOCITY) / (HIT_MAX_VELOCITY - HIT_MIN_VELOCITY);
                        float hit_volume = HIT_MIN_VOLUME + (1.0f - HIT_MIN_VOLUME)*hit_velocity_factor;

                        s_sndHitGround.Play(hit_volume, hit_volume, 0.0f);
                    }

                    UpdateHitBox();
                    break;

                case State.GROUND:
                    // roll that shit
                    break;
            }

            _game.GetShadowHelper().AddShadow(_hit_box.Center.X, _hit_box.Bottom, SHADOW_WIDTH, SHADOW_THICKNESS);
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            switch (_state)
            {
                case State.SPAWNING:
                    // apply some scaling during spawning
                    float scale_factor = MathHelper.SmoothStep(1.0f, 0.0f, _timer / SPAWN_DURATION);
                    spriteBatch.Draw(s_texNut, _position, s_rectAcorn, Color.White, 0.0f, s_spriteOriginHung, scale_factor, SpriteEffects.None, 0.0f);
                    break;

                case State.HANGING:
                    // apply some oscillating during hanging
                    spriteBatch.Draw(s_texNut, _position, s_rectAcorn, Color.White, _rotation, s_spriteOriginHung, 1.0f, SpriteEffects.None, 0.0f );
                    break;

                case State.FREE:
                case State.GROUND:
                    // when free, nut position is its center, and it spins around it
                    spriteBatch.Draw(s_texNut, _position, s_rectAcorn, Color.White, _rotation, s_spriteOriginLoose, 1.0f, SpriteEffects.None, 0.0f);
                    break;
            }
        }

        protected void UpdateHitBox()
        {
            _hit_box.X = (int)_position.X - Nut.SIZE/2;
            _hit_box.Y = (int)_position.Y - Nut.SIZE/2;

            // dimensions don't change
        }

        protected Vector2 ComputeHangingNutDirection(float rotation)
        {
            return new Vector2(-(float)Math.Sin((double)rotation), (float)Math.Cos((double)rotation));
        }

        protected Vector2 ComputeHangingNutCenter(float rotation)
        {
            Vector2 nut_direction = ComputeHangingNutDirection(rotation);
            Vector2 center = new Vector2();
            center = _position + nut_direction * 0.5f * (float)s_rectAcorn.Height;

            return center;
        }         
    }
}
